import {
  FormulaElement,
  useFormulaEditor,
  Variable,
} from '@/store/3d/formulaEditor'
import * as THREE from 'three'
import {
  GeometryShape,
  GeometryType,
  ShapesType,
} from '@/graph/3d/config/shapes'
import { getPoints } from '@/utils/math'
import { PointerGenerator } from '@/views/3d/hooks/usePointer'
import { cloneDeep } from 'lodash'
import ShapeGenerator from '@/views/3d/hooks/shapes'
import { parse } from 'mathjs'

let id = -1
let subId = -1
const name = 'ABCDEFGHIJKJMNOPQRSTUVW'
const subName = 'abcdefghijklmnopqrstuvw'

export interface FElementOptions {
  type: GeometryType
}

export class FElement implements FormulaElement {
  id: string
  type: GeometryType
  name: string
  content: string
  visible: boolean
  style: any
  shapes: ShapesType[]
  refs: string[]
  points: string[]
  variable: Variable
  result: any
  error: string
  dependencies: Set<string>
  isShape: boolean
  formulaStr: string
  constructor(options?: Partial<FormulaElement>) {
    this.name = ''
    this.shapes = []
    this.refs = []
    this.points = []
    this.type = options?.type || GeometryType.Function
    this.style = {
      color: 'red',
      fontSize: '20px',
      fontWeight: 'bold',
      fontFamily: 'Arial',
      textAlign: 'center',
    }
    this.variable = {
      name: '',
      value: 1,
      max: 5,
      min: -5,
      step: 0.1,
      playing: false,
      direction: 1,
      speed: 1,
    }
    this.visible = true
    this.result = 0
    this.content = options?.content || ''
    const uuid = THREE.MathUtils.generateUUID()
    this.dependencies = new Set()
    this.error = ''
    this.isShape = false
    this.formulaStr = ''

    Object.assign(this, options)
    this.id = uuid
  }
  clone() {
    return new FElement(this)
  }
  reset() {
    this.content = ''
  }
  parse() {
    try {
      // 过滤空格并检查空内容
      const content = this.content.replace(/\s/g, '')
      if (!content) {
        this.type = GeometryType.Invalid
        this.error = '内容不能为空'
        return
      }

      // 变量关联
      // w=10  -> obj
      // a=2*w -> obj
      // b=a+10
      // c=b+20
      // A=(a, b, c)

      // A=(sin(2*10), sin(2*10+10), sin(2*10+10+20))
      // z=sin(x)*cos(y)

      /** 类型匹配表（按优先级排序）*/
      const typeMatchers = [
        { pattern: /^[A-Z]=/, type: GeometryType.Pointer },
        { pattern: /^cube\((.*?)\)$/i, type: GeometryType.Cube, isShape: true },
        {
          pattern: /^plane\((.*?)\)$/i,
          type: GeometryType.Plane,
          isShape: true,
        },
        {
          pattern: /^sphere\((.*?)\)$/i,
          type: GeometryType.Sphere,
          isShape: true,
        },
        { pattern: /^line\((.*?)\)$/i, type: GeometryType.Line, isShape: true },
        {
          pattern: /^vector\((.*?)\)$/i,
          type: GeometryType.Vector,
          isShape: true,
        },
        {
          pattern: /^slider\((.*?)\)$/i,
          type: GeometryType.Slider,
          isShape: true,
        },
        { pattern: /^[a-w]=/i, type: GeometryType.Variable },
      ]

      // 顺序匹配类型
      const matched = typeMatchers.find(({ pattern }) => pattern.test(content))
      this.type = matched?.type ?? GeometryType.Function
      this.isShape = matched?.isShape ?? false

      if ([GeometryType.Pointer, GeometryType.Variable].includes(this.type)) {
        this.name = this.content.split('=')[0]
      } else {
        subId++
        this.name = subName[subId % subName.length]
      }

      parseShapes(this)

      if (this.type === GeometryType.Variable) {
        this.shapes = []

        const [name, value] = this.content.split('=')

        // value为数字（包含小数点）
        if (/^[0-9]*\.?[0-9]+$/.test(value)) {
          this.result = Number(value)
        } else if (/^[a-w]/.test(value)) {
          const formulaEditor = useFormulaEditor()
          const item = formulaEditor.elements.find((el) => el.name === value)
          if (item) {
            this.result = item.result
          }
        }
      }
      if ([GeometryType.Slider, GeometryType.Variable].includes(this.type)) {
        const params = getBracketContent(this.content).map(Number) as any
        const [
          value = 1,
          min = -5,
          max = 5,
          step = 0.1,
          direction = 1,
          speed = 1,
        ] = params
        this.variable = {
          name: this.name,
          value,
          min,
          max,
          step,
          playing: false,
          direction,
          speed,
        }

        if (this.type === GeometryType.Slider) {
          this.content = `Slider(${value}, ${min}, ${max}, ${step})`
        }
      }
    } catch (e) {
      console.log(e)
      this.error = '语法不正确'
    }
  }
}

function analyzeDependencies(el: FormulaElement) {
  const formulaEditor = useFormulaEditor()
  const dependencies = new Set<string>()
  const newElement: FormulaElement[] = []
  let expression = preprocess(el.content)
  const ast = parse(expression)
  const variables = new Map()

  let arrayNodeDepth = 0,
    symbolNodeDepth = 0
  ast.traverse((node) => {
    // @ts-ignore
    if (
      node.type === 'SymbolNode' &&
      // @ts-ignore
      node.name &&
      // @ts-ignore
      !['x', 'y', 'z'].includes(node.name)
    ) {
      // @ts-ignore
      dependencies.add(node.name)

      symbolNodeDepth++
      if (symbolNodeDepth > 1) {
        const flag = formulaEditor.elements.some(
          // @ts-ignore
          (element) => element.name === node.name,
        )
        if (!flag) {
          const element = new FElement({
            type: GeometryType.Variable,
            // @ts-ignore
            name: node.name,
            // @ts-ignore
            content: node.name,
          })
          newElement.push(element)
        }
      }
    }
    // plane((1,2,3),(2,3,4), (4,5,6))
    // @ts-ignore
    if (node.type === 'ArrayNode' && node.items) {
      arrayNodeDepth++
      if (arrayNodeDepth > 1 || el.isShape) {
        const _name = `${name[++id % name.length]}`
        // @ts-ignore
        const value = `(${node.items.map((item) => item.toString()).join(',')})`
        const content = `${_name}=${value}`
        const element = new FElement({
          type: GeometryType.Pointer,
          name: _name,
          content,
        })
        element.parse()

        variables.set(value, _name)
        dependencies.add(_name)

        newElement.push(element)
      }
    }
  })
  expression = convertBrackets(expression)
  variables.forEach((value, key) => {
    expression = expression.replace(key, value)
  })

  return { dependencies, ast, newElement, expression }
}
// 预处理字符串
function preprocess(input: string) {
  return input
    .replace(/\(/g, '[') // 将圆括号转为方括号
    .replace(/\)/g, ']')
    .replace(/\s+/g, '') // 去掉所有空格
}
// 将方括号转为圆括号
function convertBrackets(input: string) {
  return input.replace(/\[/g, '(').replace(/\]/g, ')')
}
// 获取括号内的内容
function getBracketContent(input: string) {
  const regex = /\((.*?)\)$/g
  let match,
    content: string = ''

  while ((match = regex.exec(input)) !== null) {
    content = match[1]
  }

  if (!content) return []

  // 分割逗号的内容（不包含括号内的字符）
  return content.split(/(?<!\([^)]*),(?![^()]*\))/g)
}

function parseVariable(element: FormulaElement) {
  const formulaEditor = useFormulaEditor()

  const { dependencies, ast, newElement, expression } =
    analyzeDependencies(element)
  element.dependencies = dependencies
  element.content = expression

  formulaEditor.addElement(newElement)
  formulaEditor.updateDependencyGraph(element.id, element.dependencies)

  const names = Array.from(element.dependencies)
  const scope: any = {}
  formulaEditor.elements
    .filter((el) => names.includes(el.name))
    .forEach((el) => {
      scope[el.name] = el.result
    })
  const code = ast.compile()
  try {
    element.result = code.evaluate(scope)
  } catch {
    element.result = element.variable.value
  }
}

function parseShapes(element: FormulaElement) {
  if (!element.content) return element

  parseVariable(element)

  const item = {
    toRef: element.id,
  } as ShapesType

  switch (element.type) {
    case GeometryType.Function: {
      if (element.content.includes('=')) {
        const obj = ShapeGenerator.create(element.type, [], {
          name: element.name,
          formulaStr: element.content,
        })

        Object.assign(item, cloneDeep(obj))
      } else {
        const vertices = getPoints(element.content)

        Object.assign(item, {
          id: element.id,
          name: element.name,
          vertices,
          formulaId: element.id,
        })
      }
      break
    }
    case GeometryType.Pointer: {
      const result = element.result._data
      const position = result.splice(0, 3)

      const _pointer = element.shapes[0]

      const pointer = new PointerGenerator({
        position,
        canMove: true,
        name: element.name,
        formulaId: element.id,
      })

      Object.assign(item, pointer, _pointer, { position })

      break
    }
    case GeometryType.Slider:
    case GeometryType.Variable: {
      break
    }
    default: {
      const formulaEditor = useFormulaEditor()
      const str = getBracketContent(element.content)
      if (str.length) {
        const points = str.map((item) => {
          if (item.includes('(')) {
            const elment = new FElement({ content: `A=${item}` })
            elment.parse()
            formulaEditor.addElement(elment)

            return elment.name
          }
          return item
        })

        const pointers = formulaEditor.elements.filter(
          (item) =>
            item.type === GeometryType.Pointer && points.includes(item.name),
        )

        const els = pointers.map((item) => item.shapes[0])
        const obj = ShapeGenerator.create(element.type, els as any, {
          name: element.name,
          formulaId: element.id,
        })

        Object.assign(item, cloneDeep(obj))
      }
      break
    }
  }

  element.shapes = [item]

  return element
}

export function pointer2shape(element: FormulaElement) {
  const formulaEditor = useFormulaEditor()

  const name = element.name
  const ids = formulaEditor.dependencyGraph.get(name)

  const els = formulaEditor.elements.filter((item) => ids?.includes(item.id))

  const _els = els.map((item) => parseShapes(item))
  formulaEditor.updateElement(_els)
}
